#pragma once

#include <iostream>
#include <string>
#include <pthread.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <unordered_map>
#include "Socket.hpp"
#include "Log.hpp"

const std::string wwwroot = "./wwwroot"; // web根目录
const std::string sep = "\r\n";
const std::string homepage = "index.html"; // 默认首页

static const int defaultport = 8080;

class HttpServer;

class ThreadData
{
public:
    ThreadData(int fd, HttpServer *s) : sockfd(fd), svr(s)
    {
    }

public:
    int sockfd;
    HttpServer *svr;
};

class HttpRequest
{
public:
    void Deserialize(std::string req)
    {
        while (true)
        {
            std::size_t pos = req.find(sep);
            if (pos == std::string::npos)
                break;
            std::string temp = req.substr(0, pos);
            if (temp.empty())
                break;
            req_header.push_back(temp);
            req.erase(0, pos + sep.size());
        }
        text = req;
    }

    // 对请求进行请求行，请求报头，请求正文的解析
    //  .png:image/png
    void Parse()
    {
        std::stringstream ss(req_header[0]); // stringstream以空格为分隔符分割字符串
        ss >> method >> url >> http_version;
        file_path = wwwroot;                    // ./wwwroot
        if (url == "/" || url == "/index.html") // 不指明访问网站的资源路径，默认url会带一个/，但是网站不会直接让你访问web根目录下的所有资源，所以默认就是首页
        {
            file_path += "/";
            file_path += homepage; // ./wwwroot/index.html
        }
        else
            file_path += url; // /a/b/c/d.html->./wwwroot/a/b/c/d.html

        // auto pos = file_path.rfind(".");
        // if (pos == std::string::npos)
        //     suffix = ".html";
        // else
        //     suffix = file_path.substr(pos);
    }
    void DebugPrint()
    {
        for (auto &line : req_header)
        {
            std::cout << "--------------------------------" << std::endl;
            std::cout << line << "\n\n";
        }

        std::cout << "method: " << method << std::endl;
        std::cout << "url: " << url << std::endl;
        std::cout << "http_version: " << http_version << std::endl;
        std::cout << "file_path: " << file_path << std::endl;
        std::cout << text << std::endl;
    }

public:
    std::vector<std::string> req_header;
    std::string text;

    // 解析之后的结果
    std::string method;
    std::string url;
    std::string http_version;
    std::string file_path; // ./wwwroot/a/b/c.html 2.png

    std::string suffix;
};

class HttpServer
{
public:
    HttpServer(uint16_t port = defaultport) : port_(port)
    {
        content_type.insert({".html", "text/html"});
        content_type.insert({".png", "image/png"});
    }

    bool Start()
    {
        listen_sockfd_.Socket();
        listen_sockfd_.Bind(port_);
        listen_sockfd_.Listen();
        for (;;)
        {
            std::string clientip;
            uint16_t clienport;
            int sockfd = listen_sockfd_.Accept(&clientip, &clienport);
            if (sockfd < 0)
                continue;
            lg(Info, "get a new connect,sockfd: %d", sockfd);
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this); // this类型是HttpServer
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }

    static std::string ReadHtmlContent(const std::string &htmlpath)
    {
        // 坑
        std::ifstream in(htmlpath, std::ios::binary);
        if (!in.is_open())
            return "";

        //读取文件大小
        in.seekg(0, std::ios_base::end);
        auto len = in.tellg();
        in.seekg(0, std::ios_base::beg);

        //然后读文件，由于带有二进制，所以采用的是可以放到堆空间，再以二进制的方式从堆空间读
        //也可以放到vector里面，再通过send发过去
        //这里采用的是直接根据文件大小开空间，将文件的内容直接读到content中
        std::string content;
        content.resize(len);

        in.read((char *)content.c_str(), content.size());
        // std::string content;
        // std::string line;
        // while(std::getline(in, line))
        //{
        //     content += line;
        // }

        in.close();

        return content;
    }

    // 对文件的后缀做解析，知道是什么文件才能填报头的content_type字段，然后让浏览器做解析
    std::string SuffixToDesc(const std::string &suffix)
    {
        auto iter = content_type.find(suffix);
        if (iter == content_type.end())
            return content_type[".html"];
        else
            return content_type[suffix];
    }

    void HandlerHttp(int sockfd)
    {
        // 这里读取请求有bug，还需要保证读到的请求是一个完整的，所以还要做序列化等工作,假设当前recv读到的就是一个完整的http,所以代码可能出现段错误
        char buffer[10240];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0); // 比read多了一个参数flags，设为0就和read一样
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << buffer; // 假设buffer中读到的就是一个完整的http请求，真正的还需要做按行做分隔符等处理
            HttpRequest req;
            req.Deserialize(buffer);
            req.Parse();
            // req.DebugPrint();

            // std::string path = wwwroot;
            // path += url; // wwwroot/a/b/c/index.html

            // 返回响应的过程
            std::string text;
            bool ok = true;
            text = ReadHtmlContent(req.file_path);
            if (text.empty())
            {
                ok = false;
                std::string err_html = wwwroot;
                err_html += "/";
                err_html += "err.html";
                text = ReadHtmlContent(err_html);
            }

            std::string response_line;
            if (ok)
                response_line = "HTTP/1.0 200 OK\r\n";
            else
                response_line = "HTTP/1.0 404 Not Found\r\n";

            // response_line = "HTTP/1.0 302 Found\r\n";
            std::string response_header = "Content-Length: ";
            response_header += std::to_string(text.size()); // Content-Length:11
            response_header += "\r\n";
            response_header += "Content-Type: ";
            response_header += SuffixToDesc(req.suffix);
            response_header += "\r\n";
            response_header += "Set-Cookie: name=haha";
            response_header += "\r\n";
            response_header += "Set-Cookie: passwd=123456";
            response_header += "\r\n";
            response_header += "Set-Cookie: view=hello.html";
            response_header += "\r\n";
            // response_header+="Location:http://www.qq.com\r\n";
            std::string blank_line = "\r\n";

            std::string response = response_line;
            response += response_header;
            response += blank_line;
            response += text;

            send(sockfd, response.c_str(), response.size(), 0); // 比write多了一个参数flags，设为0就和write一样
        }
        close(sockfd);
    }

    // 由于ThreadRun函数不能有this指针，所以需要变成静态的
    // 又因为静态成员只能调用静态方法，所以HandlerHttp也要是静态的
    // 怎么做到HandlerHttp不是静态的但是ThreadRun又能调到这个方法呢？
    // 在td是ThreadData类型的，在ThreadData类型中多一个成员HttpServer* svr成员
    // 在构造td的时候（这里指的是传给args的实参）将this指针传进去，this指针类型是HttpServer的
    // 这样就能通过td调用到成员svr，再通过svr调用到HandlerHttp了
    static void *ThreadRun(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->svr->HandlerHttp(td->sockfd);
        delete td;
        return nullptr;
    }

    ~HttpServer()
    {
    }

private:
    Sock listen_sockfd_;
    uint16_t port_;
    std::unordered_map<std::string, std::string> content_type;
};